package com.time.workbench.websocket.service;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jcraft.jsch.*;
import com.time.workbench.efficient.consts.CmdConst;
import com.time.workbench.efficient.entity.bo.DeployRecord;
import com.time.workbench.efficient.entity.bo.Server;
import com.time.workbench.efficient.service.DeployRecordService;
import com.time.workbench.efficient.service.ServerService;
import com.time.workbench.efficient.util.IpUtil;
import com.time.workbench.release.consts.ReleaseConst;
import com.time.workbench.websocket.consts.ConstantPool;
import com.time.workbench.websocket.entity.SSHConnectInfo;
import com.time.workbench.websocket.entity.WebSSHData;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;

@Service
@Slf4j
@RequiredArgsConstructor
public class WebSSHService {
    private Map<String, SSHConnectInfo> sshMap = new HashMap<>();
    //线程池
    private ExecutorService executorService = Executors.newCachedThreadPool();

    private final ServerService serverService;
    private final DeployRecordService deployRecordService;

    public void initConnection(WebSocketSession session) {
        JSch jSch = new JSch();
        SSHConnectInfo sshConnectInfo = new SSHConnectInfo();
        sshConnectInfo.setJSch(jSch);
        sshConnectInfo.setWebSocketSession(session);
        String uuid = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
        //将这个ssh连接信息放入map中
        sshMap.put(uuid, sshConnectInfo);
    }

    public void recvHandle(String buffer, WebSocketSession session) {
        ObjectMapper objectMapper = new ObjectMapper();
        WebSSHData webSSHData = null;
        try {
            //转换前端发送的JSON
            webSSHData = objectMapper.readValue(buffer, WebSSHData.class);
            log.info("接受命令:{}", JSON.toJSONString(webSSHData));
        } catch (IOException e) {
            log.error("Json转换异常");
            log.error("异常信息:{}", e.getMessage());
            return;
        }
        //获取刚才设置的随机的uuid
        String userId = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
        if (ConstantPool.WEBSSH_OPERATE_CONNECT.equals(webSSHData.getOperate())) {
            //如果是连接请求
            //找到刚才存储的ssh连接对象
            SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
            //启动线程异步处理
            WebSSHData finalWebSSHData = webSSHData;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        DeployRecord deployRecord = deployRecordService.getDeployByCode(finalWebSSHData.getDeployCode());
                        Server server = serverService.getById(deployRecord.getServerId());
                        //连接到终端
                        connectToSSH(sshConnectInfo, finalWebSSHData, session, deployRecord, server, (channel) -> {
                            try {
                                transToSSH(channel, StrUtil.format(CmdConst.cd, deployRecord.getDeployPath())+" \r");
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        });
                        // cd到指定目录下

                    } catch (JSchException | IOException e) {
                        log.error("webssh连接异常");
                        log.error("异常信息:{}", e.getMessage());
                        close(session);
                    }
                }
            });
        } else if (ConstantPool.WEBSSH_OPERATE_COMMAND.equals(webSSHData.getOperate())) {
            //如果是发送命令的请求
            String command = webSSHData.getCommand();
            SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
            if (sshConnectInfo != null) {
                try {
                    //发送命令到终端
                    transToSSH(sshConnectInfo.getChannel(), command);
                } catch (IOException e) {
                    log.error("webssh连接异常");
                    log.error("异常信息:{}", e.getMessage());
                    close(session);
                }
            }
        } else {
            log.error("不支持的操作");
            close(session);
        }
    }

    public void sendMessage(WebSocketSession session, byte[] buffer) throws IOException {
        session.sendMessage(new TextMessage(buffer));
    }

    public void close(WebSocketSession session) {
        //获取随机生成的uuid
        String userId = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
        SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
        if (sshConnectInfo != null) {
            //断开连接
            if (sshConnectInfo.getChannel() != null) sshConnectInfo.getChannel().disconnect();
            //map中移除该ssh连接信息
            sshMap.remove(userId);
        }
    }

    /**
     * @Description: 使用jsch连接终端
     * @Param: [cloudSSH, webSSHData, webSocketSession]
     * @return: void
     * @Author: NoCortY
     * @Date: 2020/3/7
     */
    private void connectToSSH(SSHConnectInfo sshConnectInfo, WebSSHData webSSHData, WebSocketSession webSocketSession, DeployRecord deployByCode, Server server, Consumer<Channel> connectSuccessConsumer) throws JSchException, IOException {
        Session session = null;
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no");


        //获取jsch的会话
        boolean privateIsConnection = !StrUtil.isEmpty(server.getPrivateIp()) && IpUtil.isHostReachable(server.getPrivateIp(), 3000);
        String serverIp = privateIsConnection ? server.getPrivateIp() : server.getPublicIp();
        session = sshConnectInfo.getJSch().getSession(server.getUsername(), serverIp, server.getPort());
        session.setConfig(config);
        //设置密码
        SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES,  ReleaseConst.ServerKey.getBytes(StandardCharsets.UTF_8));
        String passwordDis = aes.decryptStr(server.getPassword(), CharsetUtil.CHARSET_UTF_8);
        session.setPassword(passwordDis);
        //连接  超时时间30s
        session.connect(30000);

        //开启shell通道
        Channel channel = session.openChannel("shell");
        ((ChannelShell) channel).setPtyType("vt100",webSSHData.getExtra().getCols(),webSSHData.getExtra().getRows(),webSSHData.getExtra().getWidth(),webSSHData.getExtra().getHeight());

        //通道连接 超时时间3s
        channel.connect(3000);
        //设置channel
        sshConnectInfo.setChannel(channel);

        //转发消息
        transToSSH(channel, "\r");
        if (connectSuccessConsumer != null)
            connectSuccessConsumer.accept(channel);

        //读取终端返回的信息流
        InputStream inputStream = channel.getInputStream();
        try {
            //循环读取
            byte[] buffer = new byte[1024];
            int i = 0;
            //如果没有数据来，线程会一直阻塞在这个地方等待数据。
            while ((i = inputStream.read(buffer)) != -1) {
                sendMessage(webSocketSession, Arrays.copyOfRange(buffer, 0, i));
            }

        } finally {
            //断开连接后关闭会话
            session.disconnect();
            channel.disconnect();
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * @Description: 将消息转发到终端
     * @Param: [channel, data]
     * @return: void
     * @Author: NoCortY
     * @Date: 2020/3/7
     */
    private void transToSSH(Channel channel, String command) throws IOException {
        if (channel != null) {
            OutputStream outputStream = channel.getOutputStream();
            outputStream.write(command.getBytes());
            outputStream.flush();
        }
    }

    public static void main(String[] args) {
        System.out.println("ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss".length());
    }

}
